/*
* 该模块主要完成离线文档的数据清洗工作
* 并将清洗完之后的一个文档中的所有内容
* 以一行数据的形式保存在项目的 data/raw_html/raw.txt 中
*/
#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "util.hpp"
#include "log.hpp"

const std::string src_path = "data/input";          // 离线html文件的存放路径
const std::string output = "data/raw_html/raw.txt"; // 清洗完之后的文档数据存放的路径

typedef struct DocInfo{
    std::string title;   //文档的标题
    std::string content; //文档内容
    std::string url;     //该文档在官网中的url
}DocInfo_t;

// 提取所有html文件的路径，并保存在files_list中
bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list);
// 根据files_list，把每个html文件转换为DocInfo_t类型的数据
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t>& results);
// 把每一个DocInfo_t类型的数据转换成一行数据保存在项目的"data/raw_html/raw.txt"文件中
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output);

int main()
{
    // 第一步: 递归式的把每个html文件名带路径，保存到files_list中，方便后期进行一个一个的文件进行读取
    std::vector<std::string> files_list;
    if(!EnumFile(src_path, &files_list))
    {
        LOG(ERROR,"%s not exists\n", src_path.c_str());
        return 1;
    }

    LOG(INFO,"提取html文件路径名成功\n");

    // 第二步: 按照files_list读取每个文件的内容，将内容进行数据清洗，并构造DocInfo_t类型的对象
    std::vector<DocInfo_t> results;
    if(!ParseHtml(files_list, results))
    {
        LOG(ERROR,"parse html error\n");
        return 2;
    }

    LOG(INFO,"已对所有html文件进行数据清洗，并构建DocInfo表成功\n");

    // 第三步: 把vector<DocInfo_t>中的一个个对象转换成一行存储在 "data/raw_html/raw.txt" 中
    if(!SaveHtml(results, output))
    {
        LOG(ERROR,"sava html error\n");
        return 3;
    }

    LOG(INFO,"根据DocInfo表构建保存数据到原始文件成功\n");

    return 0;
}

bool EnumFile(const std::string& src_path, std::vector<std::string>* files_list)
{
    namespace fs = boost::filesystem;
    fs::path root_path(src_path);

    // 判断路径是否存在
    if(!fs::exists(root_path))
    {
        LOG(ERROR,"%s not exists\n", src_path.c_str());
        return false;
    }

    // bool flag = true; // for test
    //定义一个空的迭代器，用来进行判断递归结束
    fs::recursive_directory_iterator end;
    for(fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
    {
        // 我们需要的文件必须普通文件，而且是html文件
        if(fs::is_regular_file(*iter) && iter->path().extension() == ".html")
        {
            // 将所有带路径的html保存在files_list,方便后续进行文本分析
            // 提取出的路径个数："data/input/BOOST_YAP__1_3_49_8_2_7_10.html" 
            files_list->push_back(iter->path().string()); 
        }
        
        // if(flag)
        // {
        //     std::cout << iter->path() << std::endl;
        //     flag = false;
        // }
    }

    return true;
}

static bool GetTitle(const std::string &file, std::string *title)
{
    // 计算标题的头
    auto begin = file.find("<title>");
    if(begin == std::string::npos)
    {
        return false;
    }

    begin += std::string("<title>").size();

    // 计算标题的尾
    auto end = file.find("</title>");
    if(end == std::string::npos)
    {
        return false;
    }

    if(begin > end)
    {
        return false;
    }

    *title = file.substr(begin, end - begin);

    return true;
}

static bool GetContent(const std::string &file, std::string *content)
{
    //去标签,基于一个简易的状态机
    enum status{
        LABLE,
        CONTENT
    };

    enum status flag = LABLE;
    for(char c : file){
        switch(flag)
        {
        case LABLE:
            if(c == '>') flag = CONTENT;
            break;
        case CONTENT:
            if(c == '<') 
            {
                flag = LABLE;
            }
            else 
            {
                //我们不想保留原始文件中的\n,因为我们想用\n作为html解析之后文本的分隔符
                if(c == '\n') c = ' ';
                content->push_back(c);
            }
            break;
        default:
            break;
        }
    }

    return true;
}

static bool GetUrl(const std::string &file_path, std::string *url)
{
    std::string url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html";
    std::string url_tail = file_path.substr(src_path.size());

    *url = url_head + url_tail;
    return true;
}

//for test
static void ShowDoc( const DocInfo_t &doc)
{
    std::cout << "title: " << doc.title << std::endl;
    std::cout << "content: " << doc.content << std::endl;
    std::cout << "url: " << doc.url << std::endl;
}

bool ParseHtml(const std::vector<std::string>& files_list, std::vector<DocInfo_t>& results)
{
    for(const std::string& file_path : files_list){
        // 1.根据文件名读取文件内容
        std::string result;
        if(!ns_util::FileUtil::ReadFile(file_path, &result))
        {
            LOG(ERROR,"read file error: %s\n", file_path.c_str());
            continue;
        }

        // 2.根据文件内容构造DocInfo_t对象
        DocInfo_t doc;

        // 2.1 提取文件中的<title>标签中的内容构建 doc.title 
        if(!GetTitle(result, &doc.title))
        {
            continue;
        }
        // 2.2 提取文件去标签之后的内容构建 doc.content 
        if(!GetContent(result, &doc.content))
        {
            continue;
        }
        // 2.3 构建 doc.url
        if(!GetUrl(file_path, &doc.url))
        {
            continue;
        }

        // 成功构建当前文档的DocInfo_t对象
        results.push_back(std::move(doc));
    }

    return true;
}

// 文件的标题、内容、url之间用\3分割
// 文件内容和文件内容之间用\n分割，便于读取文件的内容
#define SEP '\3'
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
    //按照二进制方式进行写入
    std::ofstream out(output, std::ios::out | std::ios::binary);
    if(!out.is_open())
    {
        LOG(FATAL,"open %s failed!\n", output.c_str());
        return false;
    }

    //就可以进行文件内容的写入了
    for(auto& doc: results)
    {
        std::string one_line_text;
        one_line_text += doc.title;
        one_line_text += SEP;
        one_line_text += doc.content;
        one_line_text += SEP;
        one_line_text += doc.url;
        one_line_text += '\n';

        out.write(one_line_text.c_str(), one_line_text.size());
    }

    out.close();

    return true;
}